//! Defines scopes for grouping keys.
use serde::{Deserialize, Serialize};
use strum_macros::{EnumDiscriminants, EnumIter, EnumString, IntoStaticStr};
#[cfg(test)]
mod tests;

/// A scope is a label for grouping keys.
///
/// A key with scope `A` is in scope `B` iff `A == B`.
///
/// # Requirements
/// * Key scopes MUST be suitable for long term forwards and backwards
///   compatibility.
/// * Key scopes SHOULD be designed in a way that allows efficient querying of a
///   large collection of keys.
/// * Scopes SHOULD use a small, bounded amount of memory.  E.g. IPv6 addresses,
///   small const strings, enum variants, Internet Computer identities.
///   * The bounded size representation is enforced by the Copy trait.
///   * The maximum size SHOULD be checked in a test.
/// * Scopes MAY be used outside the CSP and WILL appear in logging so SHOULD
///   have a stable, small, efficient, ergonomic string representation.
/// * Scopes MAY have to represent entities external to the crypto component,
///   such as Ids, however scopes SHOULD not depend on these external types but
///   rather on the minimal required properties of these types; e.g. a scope
///   might support all external identities that can be represented as byte
///   arrays.
/// * Scopes MUST NOT restrict our ability to add new scopes; future
///   functionality may include scopes for delegated keys, canister keys and
///   others and that design space must not be restricted.
#[derive(
    Copy, Clone, Eq, PartialEq, Debug, Deserialize, EnumDiscriminants, IntoStaticStr, Serialize,
)]
// Create an enum `ScopeSchemeNames` used for string matching.
#[strum_discriminants(name(ScopeSchemeNames))]
#[strum_discriminants(derive(EnumString))]
pub enum Scope {
    /// Simple scheme with no hierarchy.
    ///
    /// Note: This is the first scheme and is intended to be as simple as
    /// possible.  Indexing entries, each of which is optionally labelled by one
    /// of a finite list of labels, is simple.  The string representation of the
    /// labels has no syntactic structure.  Integers may be used in place of
    /// strings.  There is no nesting.
    Const(ConstScope),
}

/// A serialisation to String.  This is guaranteed to be stable and SHOULD be
/// efficient.
///
/// # Errors
/// This method can return io errors if the formatter is writing to a file or
/// similar fallible output, otherwise this is infallible.
impl std::fmt::Display for Scope {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let scheme_name: &'static str = self.into();
        match self {
            Scope::Const(variant) => write!(f, "{scheme_name}:{variant}"),
        }
    }
}
impl std::str::FromStr for Scope {
    type Err = ::strum::ParseError;
    fn from_str(s: &str) -> ::std::result::Result<Scope, Self::Err> {
        // This is the equivalent of `split_once(':')` in nightly.
        let boundary = s.find(':').unwrap_or(s.len());
        let scheme = ScopeSchemeNames::from_str(&s[0..boundary])?;
        match scheme {
            ScopeSchemeNames::Const => Ok(Scope::Const(ConstScope::from_str(&s[boundary + 1..])?)),
        }
    }
}
/// String serialisation
impl From<&Scope> for String {
    fn from(scope: &Scope) -> String {
        format!("{scope}")
    }
}

/// Simple, fixed labels.
///
/// These may be encoded as integers or strings, without fear that there is some
/// semantic meaning to the characters in the string.
///
/// Adding a variant here requires no further code changes.
#[derive(
    Copy, Clone, Eq, PartialEq, Debug, Deserialize, EnumIter, EnumString, IntoStaticStr, Serialize,
)]
pub enum ConstScope {
    /// Placeholders, used for testing.
    Test0,
    Test1,
    /// Threshold keys generated by NiDKG for the IC consensus mechanism.
    NiDkgThresholdKeys,
    NiDkgFsEncryptionKeys,
    IDkgMEGaEncryptionKeys,
    IDkgThresholdKeys,
}
/// Each variant is represented by the variant name ONLY.
impl std::fmt::Display for ConstScope {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let name: &'static str = self.into();
        write!(f, "{name}")
    }
}
/// String serialisation
impl From<&ConstScope> for String {
    fn from(scope: &ConstScope) -> String {
        format!("{scope}")
    }
}
